package com.xxmw.transaction.conf;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.xxmw.transaction.properties.HttpClientPoolProperties;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
public class HttpConfig {

	@Autowired
	private HttpClientPoolProperties httpClientPoolProperties;
	
	@Bean(name = "httpRestTemplate")
	public RestTemplate restTemplate (HttpComponentsClientHttpRequestFactory factory) {
		return new RestTemplate(factory);
	}
	
	@Bean("closeableHttpClient")
	public CloseableHttpClient closeableHttpClient(HttpComponentsClientHttpRequestFactory factory) {
		return (CloseableHttpClient) factory.getHttpClient();
	}
	
	@Bean(name = "clientHttpRequestFactory")
	public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
	    /**
	     *  maxTotalConnection 和 maxConnectionPerRoute 必须要配
	     */
	    if (httpClientPoolProperties.getMaxTotalConnect() <= 0) {
	      throw new IllegalArgumentException("invalid maxTotalConnection: " + httpClientPoolProperties.getMaxTotalConnect());
	    }
	    if (httpClientPoolProperties.getMaxConnectPerRoute() <= 0) {
	      throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + httpClientPoolProperties.getMaxConnectPerRoute());
	    }
	    HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
	    try {
	      //设置信任ssl访问
	      SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();

	      httpClientBuilder.setSSLContext(sslContext);
	      HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
	      SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
	      Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
	              // 注册http和https请求
	              .register("http", PlainConnectionSocketFactory.getSocketFactory())
	              .register("https", sslConnectionSocketFactory).build();

	      //使用Httpclient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架
	      PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
	      // 最大连接数
	      poolingHttpClientConnectionManager.setMaxTotal(httpClientPoolProperties.getMaxTotalConnect());
	      // 同路由并发数
	      poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientPoolProperties.getMaxConnectPerRoute());
	      //配置连接池
	      httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
	      // 重试次数
	      httpClientBuilder.setRetryHandler(new TransactionRequestRetryHandler());

	      //设置默认请求头
	      httpClientBuilder.setDefaultHeaders(getDefaultHeaders());
	      //设置长连接保持策略
	      httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());
	      // return httpClientBuilder.build();
	    } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
	      log.error("初始化HTTP连接池出错", e);
	    }
	    RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(httpClientPoolProperties.getConnectionRequestTimout())
	                          .setConnectTimeout(httpClientPoolProperties.getConnectTimeout())
	                          .setSocketTimeout(httpClientPoolProperties.getReadTimeout())
	                          .build();
	    httpClientBuilder.setDefaultRequestConfig(requestConfig);
	    return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
	}

	public ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){
	    return (response, context) -> {
	      // Honor 'keep-alive' header
	      HeaderElementIterator it = new BasicHeaderElementIterator(
	              response.headerIterator(HTTP.CONN_KEEP_ALIVE));
	      while (it.hasNext()) {
	        HeaderElement he = it.nextElement();
	        log.info("HeaderElement:{}", JSON.toJSONString(he));
	        String param = he.getName();
	        String value = he.getValue();
	        if (value != null && "timeout".equalsIgnoreCase(param)) {
	          try {
	            return Long.parseLong(value) * 1000;
	          } catch(NumberFormatException ignore) {
	            log.error("解析长连接过期时间异常",ignore);
	          }
	        }
	      }
	      HttpHost target = (HttpHost) context.getAttribute(
	              HttpClientContext.HTTP_TARGET_HOST);
	      //如果请求目标地址,单独配置了长连接保持时间,使用该配置
	      Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpClientPoolProperties.getKeepAliveTargetHost()).orElseGet(HashMap::new)
	              .entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
	      //否则使用默认长连接保持时间
	      return any.map(en -> en.getValue() * 1000L).orElse(httpClientPoolProperties.getKeepAliveTime() * 1000L);
	    };
	}
	
	/**
	   * 设置请求头
	   *
	   * @return
	   */
	 private List<Header> getDefaultHeaders() {
	    List<Header> headers = new ArrayList<>();
	    headers.add(new BasicHeader("User-Agent",
	            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
	    headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
	    headers.add(new BasicHeader("Accept-Language", "zh-CN"));
	    headers.add(new BasicHeader("Connection", "Keep-Alive"));
	    return headers;
	 }
	 
	 /**
	  * Http重试
	  * @author xiangzuotao
	  * @2020年2月25日
	  */
	 private final class TransactionRequestRetryHandler implements HttpRequestRetryHandler {

		@Override
		public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
			if(executionCount >= httpClientPoolProperties.getRetryTimes()) {
				return false;
			}
			if (exception instanceof NoHttpResponseException) {//服务器丢掉链接重试
				return true;
			}
			if (exception instanceof SSLHandshakeException) { //不要重试SSL握手异常
				return false;
			}
			if (exception instanceof InterruptedIOException) {//超时重试
				return true;
			}
			if (exception instanceof UnknownHostException) {//目标服务器不可达
				return false;
			}
			if (exception instanceof ConnectTimeoutException) {//拒绝链接
				return false;
			}
			if (exception instanceof SSLException) {//ssl握手异常
				return false;
			}
			HttpRequest request = HttpClientContext.adapt(context).getRequest();
			if (!(request instanceof HttpEntityEnclosingRequest)) {//请求是幂等性重试
				return true;
			}
			return false;
		}
		 
	 }
}
